/**
 * OSAmI: Open Source Ambient Intelligence
 * A simple publish/subscribe/dispatch API (based on multicast communications) for constrained devices.
 * Copyright (C) Lionel Debroux 2006 (as a TER/Magistere M1 student in the LSR-IMAG-ADELE team, for a demo in the H-Omega project)
 * Copyright (C) Lionel Debroux 2010 (as an engineer in the LCIS-INPG-CTSYS team, for a pre-demo in the FR part of the OSAMI project)
 * Contact: osami-general@osami-commons.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
 */

package fr.imag.adele.homega.impl.unicast;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.DatagramSocket;
import java.util.Iterator;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Logger;

/**
 * This class is the core of the multicast manager: it handles registrations and message dispatch.
 */
public class UnicastHandlerImpl implements UnicastHandler {

	private java.util.HashMap<String,TopicRegistrationsInfo> topicRegistrationsInfo;
	private java.util.HashMap<String,UnicastDispatcherThread> threadInfo;
	private boolean hasQuit = true;

	public UnicastHandlerImpl() {
		topicRegistrationsInfo = new java.util.HashMap<String,TopicRegistrationsInfo>();
		threadInfo = new java.util.HashMap<String,UnicastDispatcherThread>();
	}

	/**
	 * Register a listener of given topic.
	 */
	public synchronized void register(String topic, int port, UnicastReceiver listener) throws IOException {

		//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Dispatcher: register("+topic+","+port+","+listener+") !");

		TopicInfo ti;
		TopicRegistrationsInfo tri;
		InetAddress ia;
		UnicastDispatcherThread mdt;

		// Turn topic into canonical representation
		// and check whether its address is valid.
		ia = InetAddress.getByName(topic);

		hasQuit = false;
		ti = new TopicInfo(ia,port);

		// Does this topic already exist ? 
		if (topicRegistrationsInfo.containsKey(ti.toString())) {
			// Yes.
			//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("\u001B[1;32mHandler: register(" + topic + "," + port + "," + listener + "): topic exists !\u001B[0;30m");
			
			// Get TopicRegistrationInfo associated to this topic,
			tri = topicRegistrationsInfo.get(ti.toString());
			// Add this listener to the topic.
			tri.addListener(listener);
		}
		else {
			// No.
			//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("\u001B[1;31mHandler: register(" + topic + "," + port + "," + listener + "): topic didn't exist !\u001B[0;30m");
			
			// Create TopicRegistrationInfo and add it into HashMap.
			tri = new TopicRegistrationsInfo(ia, port);
			topicRegistrationsInfo.put(ti.toString(), tri);
			// Add this listener to the topic.
			tri.addListener(listener);
			
			// Create new thread, add it into HashMap and start it. 
			mdt = new UnicastDispatcherThreadImpl(this, ti);
			threadInfo.put(ti.toString(), mdt);
			try {
				mdt.start();
			}
			catch (Exception e) {
				e.printStackTrace();
				return;
			}

			Thread.yield();
		}
	}

	/**
	 * Unregister a listener of given topic.
	 */
	public synchronized void unregister(String topic, int port, UnicastReceiver listener) throws IOException {

		//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Handler: unregister("+topic+","+port+","+listener+") !");

		TopicInfo ti;
		TopicRegistrationsInfo tri;
		InetAddress ia;
		UnicastDispatcherThread t;

		ia = InetAddress.getByName(topic);

		ti = new TopicInfo(ia,port);

		// Does this topic already exist ? 
		if (!(topicRegistrationsInfo.containsKey(ti.toString()))) {
			// No.
			Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("Handler: unregister(" + topic + "," + port +"," + listener + "): topic doesn't exist !");
			
			// Log event ?
			// Throwing an exception enables an attacker to discover what topics
			// are used (well, trying 16*(2^24-1)*65536 address/port combination
			// takes quite some time - but is much easier within restricted groups
			// of ports and addresses).
		}
		else {
			// Yes.
			//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Dispatcher: unregister(topic,port,listener): topic exists !");

			// Remove topic from TopicRegistrationsInfo.
			tri = topicRegistrationsInfo.get(ti.toString());
			tri.removeListener(listener);

			// Stop corresponding thread if this
			// TopicRegistrationsInfo is empty.
			if (tri.isEmpty()) {
				t = threadInfo.get(ti.toString());
				if (t == null) {
					// Oops, UnicastDispatcherThreadImpl object
					// should have been put in this hash !
					Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("Handler: unregister(topic,port,listener): thread not in hash !");
				}
				else {
					t.terminate();
				}
			}
		}
	}

	/**
	 * Register a listener that listens to all topics.
	 * Must define what this means: if this is to create a "monitor"
	 * listener, then all such listeners must be registered to all current
	 * topics, to all topics created after those listeners register
	 * themselves... much more complicated !
	 */
	/*public synchronized void register(UnicastReceiver listener) throws IOException {
		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("Handler: register("+listener+") not implemented !");
	}*/

	/**
	 * Unregister a listener of all topics it listens.
	 */
	/*public synchronized void unregister(UnicastReceiver listener) throws IOException {
		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("Handler: unregister("+listener+") not implemented !");
	}*/
	
	/**
	 * Dispatch a message.
	 */
	public synchronized void dispatch(DatagramSocket socket, DatagramPacket packet) throws IOException {
		//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Dispatcher: dispatch("+socket+","+packet+") !");

		TopicInfo ti;
		TopicRegistrationsInfo tri;
		UnicastReceiver o;
		Iterator<UnicastReceiver> it;
		String data = new String(packet.getData());
		InboundMessage message;

		Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info(data);
		try {
			message = new InboundMessage(data);
		}
		catch (Exception e) {
			try {
				throw e;
			} catch (Exception e1) {
				Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("Handler: cannot throw exception !");
				e1.printStackTrace();
				message = null;
			}
		}

		if (message != null) {
			ti = new TopicInfo(message.getTo(),message.getPortnr());

			//System.out.println(ti.toString());

			// Does this topic already exist ? 
			tri = topicRegistrationsInfo.get(ti.toString());

			if (tri != null) {
				// Yes.
				//tri.send(packet.getData());

				it = tri.getIterator();

				if (it != null) {
					// Log messages sent when nobody is registered ?
					while (it.hasNext()) {
						o = it.next();
						o.receive(ti.getAddress().toString(), message.getMessage());
					}
				}
				else {
					throw new IOException("Handler.dispatch(): cannot iterate through suscribers !");
				}
			}
			else {
				printMembers();
				throw new IOException("dispatch: topic \"" + ti.toString() + "\" doesn't exist !");
			}

			//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Dispatcher: dispatch: packet sent on " + ti + " !");
		}
	}

	public synchronized void unregisterAll(TopicInfo topic) {
		Iterator<UnicastReceiver> i;
		TopicRegistrationsInfo tri;
		UnicastReceiver listener;

		if (!(topicRegistrationsInfo.containsKey(topic.toString()))) {
			// No.
			Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("Handler: unregisterAll(" + topic.toString() + "," + topic.getPort() +"): topic doesn't exist !");
		}
		else {
			// Yes.
			//Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).info("Dispatcher: unregister(topic,port,listener): topic exists !");

			// Remove topic from TopicRegistrationsInfo.
			tri = topicRegistrationsInfo.get(topic.toString());
			i = tri.getIterator();

			if (i != null) {
				// Log messages sent when nobody is registered ?
				while (i.hasNext()) {
					listener = i.next();
					tri.removeListener(listener);
				}
			}
			else {
				Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).severe("Handler.unregisterAll(): cannot iterate through suscribers !");
			}
		}
	}

	/**
	 * Cleanup: terminate all dispatcher threads.
	 */
	private synchronized void terminateAllThreads() {
		Set<Entry<String,UnicastDispatcherThread> > set = threadInfo.entrySet();
		Iterator<Entry<String,UnicastDispatcherThread> > i = set.iterator();
		UnicastDispatcherThread t;
		while (i.hasNext()) {
			t = i.next().getValue();
			t.terminate();
		}
		hasQuit = true;
	}

	// Used for debug: print members;
	public synchronized void printMembers() {
		System.out.println(topicRegistrationsInfo.toString());
		System.out.println(threadInfo.toString());
	}

	/**
	 * Bug catcher...
	 */
	@Override
	public void finalize() {
		if (!hasQuit) {
			System.out.println("\u001B[1;5;31mForgot to quit " + super.toString() + " !\u001B[m");
			terminateAllThreads();
		}
	}

	public void shutdown() {
		terminateAllThreads();
	}
}
